-
Notifications
You must be signed in to change notification settings - Fork 349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add erc4626 #1170
base: main
Are you sure you want to change the base?
Add erc4626 #1170
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #1170 +/- ##
==========================================
- Coverage 92.26% 89.34% -2.93%
==========================================
Files 59 81 +22
Lines 1811 3471 +1660
==========================================
+ Hits 1671 3101 +1430
- Misses 140 370 +230
... and 78 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @andrew-fleming , the overall implementation looks great! I left a couple of comments, but my main suggestion is to simplify the fee mechanism.
With the current approach I feel it would be hard for users to follow how to implement fees, and is easy to misuse since adjust_[operation]
functions doesn't have a percent let it clear on which percentages are used, or on how to transfer the fees on the Hooks accordingly from the amounts received as parameters.
I think we can do something like this to simplify the logic and make it easier to follow and use:
- Have a configurable FEE_DENOMINATOR in ImmutableConfig for precision default to 10000 (100 is 1%)
- Have only two functions in the FeeConfigTrait,
entry_fee_numerator
andexit_fee_numerator
. Entry used for both deposit and mint, and exit for redeem and withdraw. - Remove the Hooks trait and add the transfer fee logic in
_deposit
and_withdraw
, with an if depending on the fee being greater than 0. - Compute the fee and apply it where it needs to be applied (preview functions).
- Maybe add a couple of internal functions like
entry_fee_set
andexit_fee_set
checking that the numerators are greater than 0.
I know this will add a couple of conditions and operations to the flow when the fees are not required (set to 0), but I think it will be negligible, and it will improve the UX a lot making it easier for users to implement a fee mechanism.
const UNDERLYING_DECIMALS: u8; | ||
const DECIMALS_OFFSET: u8; | ||
|
||
fn validate() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since UNDERLYING_DECIMALS should be equal to the asset decimals, the validate function should assert that, if the decimals function is implemented. This way it will be a constant, but it will match the correct value. We should be able to check if decimals is implemented and recover with 0.13.4.
Requirements should be updated accordingly (including the ones of initializer).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason for the having the user set UNDERLYING_DECIMALS
was to avoid requiring recoverability from a call to decimals
. If we'd prefer leveraging the forthcoming try/catch functionality, we can just automate the process like the solidity impl and remove the user input for underlying
|
||
/// Returns the maximum amount of the underlying asset that can be deposited into the Vault | ||
/// for the receiver, through a deposit call. | ||
/// If the `LimitConfigTrait` is not defined for deposits, returns 2 ** 256 - 1. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation of LimitConfigTrait must always be available, even if it is the default. We should rephrase this. I don't make any specific suggestions since you will write a better sentence for sure.
|
||
/// Allows an on-chain or off-chain user to simulate the effects of their deposit at the | ||
/// current block, given current on-chain conditions. | ||
/// If the `FeeConfigTrait` is not defined for deposits, returns the full amount of shares. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
Co-authored-by: Eric Nordelo <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job! Left a couple of comments
self.reenter_target.write(target); | ||
self.reenter_selector.write(selector); | ||
for elem in calldata { | ||
self.reenter_calldata.append().write(*elem); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we clear the calldata that already exists to allow for a repeated call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't have to. There's only one reentrant call since we're setting the reentrant Type
to No
in the hooks after the initial call
/// Those methods should be performed separately. | ||
fn redeem( | ||
ref self: TState, shares: u256, receiver: ContractAddress, owner: ContractAddress | ||
) -> u256; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we include a newline between a function and doc for the next one?
Co-authored-by: immrsd <[email protected]>
WIP
Fixes #???
PR Checklist
...still a WIP. The tests need to be refactored and improved. The idea was to make sure the logic works as expected so there's a standard (from the openzeppelin solidity tests) for any and all changes moving forward.
Some things to note and discuss:
Decimals
EIP4626 states at the end of the doc:
The OpenZeppelin solidity implementation checks for the underlying asset's tokens upon construction with a try/catch which defaults to
18
decimals if query fails. Given Starknet's current status with try/catch (✌️ dual dispatchers), this doesn't seem like a viable approach. If the vault is for an undeployed token, the deployment will fail. This PR proposes that the decimals (both underlying decimals and offset decimals) are explicitly defined by the contract through the ImmutableConfig.Math
u512 Precision math for multiply and divide (
u256_mul_div
)This PR leverages the corelib's
wide_mul
andu512_safe_div_rem_by_u256
for mathematical precision. This is set as a tmp solution and should be scrutinized further. More tests should be provided and we should look for ways to optimize.Exponentiation
This PR requires exponentiation for converting to shares and assets. The current implementation is the brute force formula. We can definitely improve this.This was added to the corelib (starkware-libs/cairo#6694). Will update when released
FeeConfigTrait
This PR proposes to utilize
FeeConfigTrait
(which is really like a hook) for contracts to integrate entry and exit fees forpreview_
fns. The state-changing methods of ERC4626 rely onpreview_
to determine the number of assets or shares to exchange.Another approach that can reduce the verbosity of the traits/hooks is to have a single
adjust_assets_or_shares
function that accepts anExchangeType
.The downside though is I think it's easy to misuse i.e.
IMO having a dedicated function for each exchange type is more difficult to mess up...but it's at the cost of some verbosity.
LimitsConfigTrait
This mirrors the
FeeConfigTrait
except that these target the limits on themax_
methods and return anOption
soOption::None
can point to the default. Same arguments apply for not having a single trait/hook with anExchangeType
parameter.before_withdraw
andafter_deposit
hooksThe
before_withdraw
andafter_deposit
hooks take inspiration from solmate's solidity implementation of erc4626. These hooks are where contracts can transfer fees calculated from theFeeConfigTrait
in the implementing contract. See the Fees mock to see how this works in the proposed PR.